namespace VolleyballHall
{
    using System.Collections.Generic;

    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Graphics;
    using Microsoft.Xna.Framework.Input;

    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game : Microsoft.Xna.Framework.Game
    {
        readonly GraphicsDeviceManager graphics;

        Matrix worldMatrix;
        Matrix viewMatrix;
        Matrix projectionMatrix;
        VertexPositionNormalTexture[] modelVertices;
        private VertexPositionNormalTexture[] modelTexturedVertices;
        private VertexPositionNormalTexture[] floorTexturedVertices;

        private List<VertexPositionNormalTexture> telebimVertices;
        private List<VertexPositionNormalTexture> fanBilboardVertices;

        private VertexPositionNormalTexture[] sphereVertices;

        private Texture2D billboard;
        private Texture2D textureFromFile;
        private Texture2D fan;
        private Texture2D floor;

        private Texture2D floorOne;
        private Texture2D floorTwo;
        private Texture2D lines;


        private float clipPlaneY;

        Effect effect;
        private Vector3 spherePosition;


        List<Model> model;
        Camera camera;
        Camera telebimCamera;


        private bool fogEnabled;
        private float fogBegins;
        private float fogEnds;

        public Game()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            worldMatrix = Matrix.Identity;
            camera = new Camera();
            telebimCamera = new Camera(new Vector3(100, -66, 22), -MathHelper.PiOver4, -MathHelper.PiOver4 / 2);
            viewMatrix = Matrix.CreateLookAt(camera.Position, camera.Position + camera.LookAt, camera.Up);

            Window.Title = "Projekt 2 - Grafika komputerowa 3D - Patrycja Skorupa";

            model = new List<Model>();

            projectionMatrix = Matrix.CreatePerspectiveFieldOfView(
                MathHelper.ToRadians(45),  // 45 degree angle
                this.GraphicsDevice.Viewport.Width /
                (float)GraphicsDevice.Viewport.Height,
                1.0f, 500.0f);


            var vertices = new List<VertexPositionNormalTexture>();

            vertices.AddRange(ModelCreator.GetCuboid(new Vector3(300, 180, 60), Vector3.Zero, -1));
            floorTexturedVertices = new VertexPositionNormalTexture[6];
            for (int i = 0; i < 6; i++)
            {
                floorTexturedVertices[i] = vertices[6];
                vertices.RemoveAt(6);
            }
            vertices.AddRange(ModelCreator.GetNet().TransformPositionBy(Matrix.CreateTranslation(0, 0, -30)));

            vertices.AddRange(ModelCreator.GetBench().TransformPositionBy(Matrix.CreateTranslation(-40, 80, -30)));
            vertices.AddRange(ModelCreator.GetBench().TransformPositionBy(Matrix.CreateTranslation(40, 80, -30)));

            modelVertices = vertices.ToArray();


            vertices = new List<VertexPositionNormalTexture>();
            vertices.AddRange(ModelCreator.GetCuboid(new Vector3(10, 10, 10), new Vector3(125, -75, 5), 1));
            modelTexturedVertices = vertices.ToArray();

            telebimVertices = ModelCreator.GetRectangle(new Vector2(16, 16));
            telebimVertices =
                telebimVertices.TransformPositionNormalBy(Matrix.CreateRotationY(2 * MathHelper.Pi / 3)).TransformPositionBy(
                    Matrix.CreateTranslation(-100, 25, 20));

            spherePosition = new Vector3(15,3,3);
            sphereVertices = ModelCreator.GetSphere(spherePosition, 4).ToArray();
            clipPlaneY = 4;
            fogEnabled = false;
            fogBegins = 10f;
            fogEnds = 100f;

            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.1
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            effect = Content.Load<Effect>("ShadingEffect");
            var lamp = Content.Load<Model>("lamp");
            ModelCreator.PrepareLamp(lamp, effect);
            model.Add(lamp);
            textureFromFile = Content.Load<Texture2D>("ad");
            floor = floorOne = Content.Load<Texture2D>("floorOne");
            floorTwo = Content.Load<Texture2D>("floorTwo");
            lines = Content.Load<Texture2D>("lines");
            var humanOne = Content.Load<Model>("human");
            fan = Content.Load<Texture2D>("fan");
            ModelCreator.PrepareHuman(humanOne, effect);
            model.Add(humanOne);
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {

        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            if (Keyboard.GetState().IsKeyDown(Keys.Left))
            {
                camera.RotateLeft();
                viewMatrix = camera.CreateViewMatrix();
            }

            if (Keyboard.GetState().IsKeyDown(Keys.Right))
            {
                camera.RotateRight();
                viewMatrix = camera.CreateViewMatrix();
            }

            if (Keyboard.GetState().IsKeyDown(Keys.Up))
            {
                camera.RotateUp();
                viewMatrix = camera.CreateViewMatrix();
            }

            if (Keyboard.GetState().IsKeyDown(Keys.Down))
            {

                camera.RotateDown();
                viewMatrix = camera.CreateViewMatrix();
            }


            if (Keyboard.GetState().IsKeyDown(Keys.A))
            {
                camera.MoveLeft();
                viewMatrix = camera.CreateViewMatrix();
            }

            if (Keyboard.GetState().IsKeyDown(Keys.D))
            {
                camera.MoveRight();
                viewMatrix = camera.CreateViewMatrix();
            }

            if (Keyboard.GetState().IsKeyDown(Keys.W))
            {
                camera.MoveUp();
                viewMatrix = camera.CreateViewMatrix();
            }

            if (Keyboard.GetState().IsKeyDown(Keys.S))
            {
                camera.MoveDown();
                viewMatrix = camera.CreateViewMatrix();
            }

            if (Keyboard.GetState().IsKeyDown(Keys.O))
            {
                clipPlaneY += 0.5f;
            }

            if (Keyboard.GetState().IsKeyDown(Keys.P))
            {
                clipPlaneY -= 0.5f;
            }

            if (Keyboard.GetState().IsKeyDown(Keys.G))
            {
               floor = floorOne;
            }

            if (Keyboard.GetState().IsKeyDown(Keys.H))
            {
               floor = floorTwo;
            }

            if (Keyboard.GetState().IsKeyDown(Keys.D1) || Keyboard.GetState().IsKeyDown(Keys.NumPad1))
            {
                fogEnabled = true;
            }

            if (Keyboard.GetState().IsKeyDown(Keys.D2) || Keyboard.GetState().IsKeyDown(Keys.NumPad2))
            {
                fogEnabled = false;
            }

            if (Keyboard.GetState().IsKeyDown(Keys.D3) || Keyboard.GetState().IsKeyDown(Keys.NumPad3))
            {
                if (fogBegins + 5 < fogEnds)
                    fogBegins += 0.5f;

            }

            if (Keyboard.GetState().IsKeyDown(Keys.D4) || Keyboard.GetState().IsKeyDown(Keys.NumPad4))
            {
                if (fogBegins > 5)
                    fogBegins -= 0.5f;
            }

            if (Keyboard.GetState().IsKeyDown(Keys.D5) || Keyboard.GetState().IsKeyDown(Keys.NumPad5))
            {
                if (fogEnds < 1000f)
                    fogEnds += 0.5f;

            }

            if (Keyboard.GetState().IsKeyDown(Keys.D6) || Keyboard.GetState().IsKeyDown(Keys.NumPad6))
            {
                if (fogBegins + 5 < fogEnds)
                    fogEnds -= 0.5f;
            }



            base.Update(gameTime);
        }

        private float s;
        

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);


            graphics.GraphicsDevice.Clear(Color.SteelBlue);

            //var rasterizerState1 = new RasterizerState { CullMode = CullMode.None };
           // graphics.GraphicsDevice.RasterizerState = rasterizerState1;

            effect.Parameters["fogEnabled"].SetValue(fogEnabled);
            effect.Parameters["fogBegins"].SetValue(fogBegins);
            effect.Parameters["fogEnds"].SetValue(fogEnds);

            effect.Parameters["clipPlaneY"].SetValue(clipPlaneY);
            
            effect.Parameters["World"].SetValue(worldMatrix);
            effect.Parameters["View"].SetValue(viewMatrix);
            effect.Parameters["Projection"].SetValue(projectionMatrix);

            effect.Parameters["activePointLights"].SetValue(1);

            effect.Parameters["LightPointPosition"].SetValue(new[] { new Vector3(0, 89, -9f) });
            effect.Parameters["LightPointColor"].SetValue(new[] { new Vector4(0f, 0.5f, 0f, 1) });


            effect.Parameters["activeSpotLights"].SetValue(2);

            effect.Parameters["lightSpotDirection"].SetValue(new[] { new Vector3(1, 1, -1), new Vector3(0, -1, -1) });
            effect.Parameters["lightSpotDecayExponent"].SetValue(new float[] { 1, 1 });
            effect.Parameters["lightSpotAngleCosine"].SetValue(new[] { 0.5f, 0.5f });


            s += 0.002f;

            effect.Parameters["LightSpotPosition"].SetValue(new[] { new Vector3(-100, -85, 25f), new Vector3(100, 85, 25f) });
            effect.Parameters["LightSpotColor"].SetValue(new[] { new Vector4(1-s, 1 - s, 1 - s, 1), new Vector4(1f, 1f, 1f, 1) });
            
            if (s > 1) s = 0;

            effect.Parameters["SpecularColor"].SetValue(new Vector4(1f, 1f, 1f, 1));
            effect.Parameters["SpecularIntensity"].SetValue(0.7f);
            effect.Parameters["SpecularPower"].SetValue(141f);

            effect.Parameters["AmbientColor"].SetValue(new Vector4(1f, 1f, 1f, 1));
            effect.Parameters["AmbientIntensity"].SetValue(0.05f);

            effect.Parameters["DiffuseIntensity"].SetValue(0.2f);


            TextureCube textureCube = CreateTextureCube();
            effect.Parameters["textureCube"].SetValue(textureCube);


            var rt = new RenderTarget2D(graphics.GraphicsDevice, 512, 512, true, SurfaceFormat.Color,
                                                   DepthFormat.Depth24Stencil8);



            GraphicsDevice.SetRenderTarget(rt);
            effect.Parameters["Projection"].SetValue(projectionMatrix);
            effect.Parameters["camPosition"].SetValue(telebimCamera.Position);
            effect.Parameters["View"].SetValue(telebimCamera.CreateViewMatrix());
            DrawAll(telebimCamera.Position, telebimCamera.Up);

            GraphicsDevice.SetRenderTarget(null);


            effect.Parameters["TVTexture"].SetValue(rt);
            effect.Parameters["View"].SetValue(viewMatrix);
            effect.Parameters["Projection"].SetValue(projectionMatrix);
            effect.Parameters["camPosition"].SetValue(camera.Position);
            DrawAll(camera.Position, camera.Up);
            base.Draw(gameTime);
        }

        private TextureCube CreateTextureCube()
        {
            effect.Parameters["Projection"].SetValue(Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver2, 1, 1, 500));

            var textureCube = new TextureCube(GraphicsDevice, 256, false, SurfaceFormat.Color);
            Vector3[] lookAt = {
                                   Vector3.UnitX, -Vector3.UnitX, -Vector3.UnitY, Vector3.UnitY, Vector3.UnitZ,
                                   -Vector3.UnitZ
                               };
            Vector3[] up = {
                               -Vector3.UnitY, -Vector3.UnitY, -Vector3.UnitZ, Vector3.UnitZ, -Vector3.UnitY,
                               -Vector3.UnitY
                           };
            CubeMapFace[] cubemapface = {
                                            CubeMapFace.PositiveX, CubeMapFace.NegativeX, CubeMapFace.NegativeY,
                                            CubeMapFace.PositiveY, CubeMapFace.PositiveZ, CubeMapFace.NegativeZ
                                        };
            effect.Parameters["camPosition"].SetValue(spherePosition);
            for (int i = 0; i < 6; i++)
            {
                effect.Parameters["View"].SetValue(Matrix.CreateLookAt(spherePosition, spherePosition + lookAt[i], up[i]));
                var sphere = new RenderTarget2D(graphics.GraphicsDevice, 256, 256, false,
                                                           SurfaceFormat.Color, DepthFormat.Depth16);
                GraphicsDevice.SetRenderTarget(sphere);
                DrawAll(spherePosition, up[i], false);
                GraphicsDevice.SetRenderTarget(null);

                var data = new Color[sphere.Width * sphere.Height];

                sphere.GetData(data);

                for (int h = 0; h < sphere.Height / 2; h++)
                {
                    for (int w = 0; w < sphere.Width; w++)
                    {
                        int one = w + h * sphere.Width;
                        int two = w + (sphere.Width - 1 - h) * sphere.Width;
                        Color temp = data[one];
                        data[one] = data[two];
                        data[two] = temp;

                    }
                }
                textureCube.SetData(cubemapface[i], data);
            }



            return textureCube;
        }

        private void PrepareBillboards(Vector3 cameraPos, Vector3 cameraUp)
        {
            fanBilboardVertices = new List<VertexPositionNormalTexture>();
            var billboards = new List<Vector3>();
            for(int i = 0;i< 7;i ++)
            billboards.Add(new Vector3(10 + i *5, 70, -15));
            foreach (var billboard in billboards)
            {
                Vector3 normal = cameraPos - billboard;
                normal.Normalize();
                Vector3 x1 = billboard + 3*cameraUp;
                Vector3 x2 = billboard - 3*cameraUp;
                Vector3 y1 = billboard + 3* Vector3.Cross(normal, cameraUp);
                Vector3 y2 = billboard - 3* Vector3.Cross(normal, cameraUp);
                

                fanBilboardVertices.AddRange(ModelCreator.GetRectangle(x1, x2, y1, y2, normal));
            }

        }



        private void DrawAll(Vector3 cameraPos, Vector3 cameraUp, bool drawSphere = true)
        {
            for (int i = -1; i < 2; i += 2)
            {
                var rs = new RasterizerState { CullMode = CullMode.None, FillMode = i == -1 ? FillMode.WireFrame : FillMode.Solid };

                graphics.GraphicsDevice.RasterizerState = rs;


                effect.Parameters["clipSide"].SetValue(i);

                if (drawSphere)
                {
                    effect.CurrentTechnique = effect.Techniques["TechniqueSphere"];
                    foreach (EffectPass pass in effect.CurrentTechnique.Passes)
                    {
                        pass.Apply();

                        graphics.GraphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleList, sphereVertices,
                                                                   0, sphereVertices.Length / 3);
                    }
                }
                effect.CurrentTechnique = effect.Techniques["TechniqueTV"];
                foreach (EffectPass pass in effect.CurrentTechnique.Passes)
                {
                    pass.Apply();

                  graphics.GraphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleList, telebimVertices.ToArray(),
                                                               0, 2);
                }
                effect.Parameters["textureFromFile"].SetValue(textureFromFile);
                effect.CurrentTechnique = effect.Techniques["TechniqueNormalTextured"];
                foreach (EffectPass pass in effect.CurrentTechnique.Passes)
                {
                    pass.Apply();

                    graphics.GraphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleList, modelTexturedVertices,
                                                               0, modelTexturedVertices.Length / 3);
                    effect.Parameters["textureFromFile"].SetValue(floor);
                    pass.Apply();
                    graphics.GraphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleList, floorTexturedVertices,
                                                               0, floorTexturedVertices.Length / 3);


                }
                effect.CurrentTechnique = effect.Techniques["TechniqueNormal"];
                foreach (EffectPass pass in effect.CurrentTechnique.Passes)
                {
                    pass.Apply();

                    graphics.GraphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleList, modelVertices,
                                                               0, modelVertices.Length / 3);
                }




                foreach (var m in model)
                    foreach (var mesh in m.Meshes)
                    {
                        mesh.Draw();
                    }
                PrepareBillboards(cameraPos, cameraUp);

                effect.CurrentTechnique = effect.Techniques["TechniqueBillboard"];
                foreach (EffectPass pass in effect.CurrentTechnique.Passes)
                {
                    effect.Parameters["billboard"].SetValue(lines);
                    pass.Apply();
                    graphics.GraphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleList,
                                                               floorTexturedVertices,
                                                               0, floorTexturedVertices.Length / 3);
                    
                    effect.Parameters["billboard"].SetValue(fan);
                    pass.Apply();

                    graphics.GraphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleList,
                                                               fanBilboardVertices.ToArray(),
                                                               0, fanBilboardVertices.Count / 3);
                    
                }
            }
        }
    }
}
